package org.dtrd.config.wechat.api.impl;

import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.dtrd.common.exception.JeecgBootException;
import org.dtrd.common.util.RedisUtil;
import org.dtrd.config.wechat.api.IWechatOfficialAccountApi;
import org.dtrd.config.wechat.config.WechatConstant;
import org.dtrd.config.wechat.config.WechatConstant.QrCodeType;
import org.dtrd.config.wechat.config.WechatUtil;
import org.dtrd.config.wechat.entity.bean.*;
import org.dtrd.config.wechat.entity.bean.officialaccount.WechatMenu;
import org.dtrd.config.wechat.entity.bean.officialaccount.WechatOfficialAccountUserInfo;
import org.dtrd.config.wechat.entity.request.WechatQrCodeRequest;
import org.dtrd.config.wechat.entity.request.officialaccount.WechatOqTagQuery;
import org.dtrd.config.wechat.entity.request.officialaccount.WxBindingTagRequest;
import org.dtrd.config.wechat.entity.response.WechatResponse;
import org.dtrd.config.wechat.entity.response.WxCreateIndividuationResponse;
import org.dtrd.config.wechat.entity.response.WxTagListResponse;
import org.dtrd.config.wechat.entity.response.WxUnionidResp;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * 微信公众号相关API
 *
 * @author xiao
 * @date 2022/3/18 5:18 PM
 */
@Slf4j
@Component
public class WechatOfficialAccountApi implements IWechatOfficialAccountApi {

    @Resource
    @Lazy
    private RedisUtil redisUtil;

    /**
     * 创建微信标签
     *
     * @param accessToken
     * @param tagName     标签名字
     * @return
     */
    @Override
    public WechatCreateTagJSON createWechatTag(AccessToken accessToken, String tagName) {
        WechatCreateTagJSON json = new WechatCreateTagJSON(tagName);
        String res = doPost(CREATE_TAG, accessToken.getAccessToken(), json);
        log.info("WechatOfficialAccountApi saveWechatTag 创建标签: {}, 返回结果: {}", tagName, res);
        return JSONUtil.toBean(res, WechatCreateTagJSON.class);
    }

    @Override
    public WxTagListResponse listWechatTag(AccessToken accessToken) {
        String res = doGet(LIST_TAG, accessToken.getAccessToken());
        return JSONUtil.toBean(res, WxTagListResponse.class);
    }

    /**
     * 绑定用户标签
     *
     * @param accessToken
     * @param openids     openids
     * @param tagid       标签id
     * @return
     */
    @Override
    public WechatResponse bindingTag(AccessToken accessToken, List<String> openids, Integer tagid) {
        WxBindingTagRequest request = new WxBindingTagRequest(openids, tagid);
        String res = doPost(BINDING_TAG, accessToken.getAccessToken(), request);
        return JSONUtil.toBean(res, WechatResponse.class);
    }

    /**
     * 查询用户标签
     *
     * @param accessToken
     * @param openid
     * @return
     */
    @Override
    public JSONObject queryUserTag(AccessToken accessToken, String openid) {
        WechatOqTagQuery request = new WechatOqTagQuery();
        request.setOpenid(openid);
        String res = doPost(QUERY_USER_TAG, accessToken.getAccessToken(), request);
        return JSONObject.parseObject(res);
    }

    @Override
    public WxCreateIndividuationResponse createConditionalMenu(AccessToken accessToken, WechatMenu menu) {
        String res = doPost(CREATE_CONDITIONAL_MENU, accessToken.getAccessToken(), menu);
        log.info("WechatOfficialAccountApi.createConditionalMenu 创建微信公众号个性化菜单: {}", res);
        return JSONUtil.toBean(res, WxCreateIndividuationResponse.class);
    }

    @Override
    public WechatResponse createCommonMenu(AccessToken accessToken, WechatMenu menu) {
        String res = doPost(CREATE_COMMON_MENU, accessToken.getAccessToken(), menu);
        log.info("WechatOfficialAccountApi.createCommonMenu 创建微信公众号默认菜单: {}", res);
        return JSONUtil.toBean(res, WechatResponse.class);
    }

    @Override
    public WechatResponse removeMenu(AccessToken accessToken) {
        String res = doGet(REMOVE_MENU, accessToken.getAccessToken());
        log.info("WechatOfficialAccountApi.removeMenu 删除微信公众号菜单");
        return JSONUtil.toBean(res, WechatResponse.class);
    }

    @Override
    public JSONObject queryOfficeAccountMenu(AccessToken accessToken) {
        String res = doGet(QUERY_MENU, accessToken.getAccessToken());
        return JSONObject.parseObject(res);
    }

    @Override
    public WechatOfficialAccountUserInfo getUserInfo(String accessToken, String openid) {
        String resultStr = doGet(GET_USER_INFO.replace(OPENID, openid), accessToken);
        WechatOfficialAccountUserInfo userInfo = JSONUtil.toBean(resultStr, WechatOfficialAccountUserInfo.class);
        if (!Objects.equals(userInfo.getErrcode(), WECHAT_RESPONSE_SUCCESS)) {
            String msg = String.format("WechatOfficialAccountApi.getUserInfo 获取用户信息失败, errcode: %d, errmsg: %s", userInfo.getErrcode(), userInfo.getErrmsg());
            log.info(msg);
            return null;
        }
        log.info("WechatOfficialAccountApi.getUserInfo 获取用户信息成功, user's unionid: {}", userInfo);
        return userInfo;
    }

    @Override
    public WxUnionidResp getUnionid(String accessToken, String openid) {
        String url = GET_UNIONID
                .replace(OPENID, openid);
        String res = doGet(url, accessToken);
        WxUnionidResp resp = JSONUtil.toBean(res, WxUnionidResp.class);
        if (resp.getErrcode().equals(WechatConstant.WECHAT_SUCCESS_CODE)) {
            log.info("WechatOfficialAccount.getUnionid 获取用户 unionid 成功: {}", resp.getUnionid());
            return resp;
        }
        log.info("WechatOfficialAccount.getUnionid 获取用户 unionid 失败: errcode = {}, errmsg = {}", resp.getErrcode(), resp.getErrmsg());
        return null;
    }

    /**
     * 校验 access_token 是否有效
     *
     * @param token  待校验的 access_token
     * @param openid openid
     * @return 校验结果
     */
    @Override
    public boolean checkOfficialAccountAccessToken(String token, String openid) {
        String url = WEB_PAGE_CHECK_TOKEN
                .replace(ACCESS_TOKEN, token)
                .replace(OPENID, openid);
        String resultStr = HttpUtil.get(url);
        WechatResponse response = JSONUtil.toBean(resultStr, WechatResponse.class);
        int errcode = response.getErrcode();
        if (errcode == WECHAT_RESPONSE_SUCCESS) {
            return true;
        } else {
            log.info("WechatApi.checkOfficialAccountAccessToken token无效: access_token: {}, openid = {}", token, openid);
            return false;
        }
    }

    /**
     * 公众号授权token
     *
     * @param code
     * @return
     */
    @Override
    public OfficialAccountAccessToken getOfficialAccountAccessToken(String code) {
        log.info("WechatApi.getOfficialAccountAccessToken.code: {}", code);
        Object cacheAccessToken = redisUtil.get(code);
        OfficialAccountAccessToken accessToken;
        if (cacheAccessToken != null) {
            log.info("WechatOfficialAccountApi.getOfficialAccountAccessToken.[code] cacheAccessToken = " + cacheAccessToken);
            accessToken = JSONUtil.toBean(StrUtil.str(cacheAccessToken, StandardCharsets.UTF_8), OfficialAccountAccessToken.class);
            log.info("WechatOfficialAccountApi.getOfficialAccountAccessToken 获取缓存token: {}", accessToken.getAccess_token());
        } else {
            String url = WEB_PAGE_ACCESS_TOKEN
                    .replace(APPID, WechatUtil.getNiTangConfig().getAppId())
                    .replace(SECRET, WechatUtil.getNiTangConfig().getAppSecret())
                    .replace(CODE, code);
            String resultStr = HttpUtil.get(url);
            log.info("WechatApi.getOfficialAccountAccessToken resultStr: {}", resultStr);
            accessToken = JSONUtil.toBean(resultStr, OfficialAccountAccessToken.class);
            if (StrUtil.isBlank(accessToken.getAccess_token())) {
                String msg = String.format("WechatApi.getOfficialAccountAccessToken 获取网页授权 access_token 失败: errcode: %d, errmsg: %s", accessToken.getErrcode(), accessToken.getErrmsg());
                log.info(msg);
                throw new JeecgBootException(msg);
            } else {
                // 刷新一下 access_token 增加有效期
                String refreshToken = accessToken.getRefresh_token();
                log.info("WechatOfficialAccountApi 刷新网页授权token: refreshToken: {}", refreshToken);
                accessToken = refreshOfficialAccountAccessToken(refreshToken);
                // jsCode : accessToken 5 分钟内有效
                redisUtil.set(code, JSONUtil.toJsonStr(accessToken), 300);
                log.info("WechatApi.getOfficialAccountAccessToken 获取网页授权 access_token 成功: {}, openid 成功: {}", accessToken.getAccess_token(), accessToken.getOpenid());
            }
        }
        return accessToken;
    }

    @Override
    public OfficialAccountAccessToken refreshOfficialAccountAccessToken(String refreshToken) {
        String url = WEB_PAGE_REFRESH_TOKEN
                .replace(APPID, WechatUtil.getNiTangConfig().getAppId())
                .replace(REFRESH_TOKEN, refreshToken);
        String resultStr = HttpUtil.get(url);
        OfficialAccountAccessToken accessToken = JSONUtil.toBean(resultStr, OfficialAccountAccessToken.class);
        if (StrUtil.isBlank(accessToken.getAccess_token())) {
            String msg = String.format("WechatApi.refreshOfficialAccountAccessToken 刷新微信公众号 access_token 失败: errcode: %d, errmsg: %s", accessToken.getErrcode(), accessToken.getErrmsg());
            log.info(msg);
            throw new JeecgBootException(msg);
        } else {
            log.info("WechatApi.refreshOfficialAccountAccessToken 刷新微信公众号 access_token 成功: {}", accessToken.getAccess_token());
        }
        return accessToken;
    }

    @Override
    public boolean checkSignature(String signature, String timestamp, String nonce, String token) {
        String[] array = new String[]{timestamp, nonce, token};
        Arrays.sort(array);
        StringBuilder sb = new StringBuilder();
        Arrays.stream(array).forEach(sb::append);
        return Objects.equals(signature, DigestUtils.sha1Hex(sb.toString()));
    }

    @Override
    public ApiTicket getApiTicket(String accessToken) {
        Map<String, Object> map = new HashMap<>(2);
        map.put("type", "jsapi");
        map.put("access_token", accessToken);
        String result = HttpUtil.get(API_TICKET_PATH, map);
        return JSONObject.parseObject(result, ApiTicket.class);
    }

    @Override
    public String doWechatSign(String apiTicket, String pageUrl, String noncestr, long timestamp) {
        StringBuilder sb = new StringBuilder();
        sb.append("jsapi_ticket").append("=").append(apiTicket)
                .append("&").append("noncestr=").append(noncestr)
                .append("&").append("timestamp=").append(timestamp)
                .append("&").append("url=").append(pageUrl);
        return DigestUtils.sha1Hex(sb.toString());
    }

    @Override
    public String getTemplateList(String accessToken) {
        String response = HttpUtil.get(GETTEMPLATELIST_PATH.replace("ACCESS_TOKEN", accessToken));
        log.info("WechatApi.getTemplateList.[accessToken] response=" + response);
        return response;
    }

    /**
     * 生成带参数的二维码
     * http请求方式: POST
     * URL: https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN
     * POST数据格式：json
     * {"action_name": "QR_LIMIT_STR_SCENE", "action_info": {"scene": {"scene_str":"test"}} }
     * 创建二维码
     *
     * @param content 二维码的内容
     * @return
     */
    @Override
    public WechatQrCodeTicket createQrCode(String accessToken, String sceneId, String content, Integer expireSeconds) {
        if (accessToken == null) {
            String errorMsg = "WechatApi.getDoctorQRCode 获取 AccessToken 失败";
            log.info(errorMsg);
            return null;
        }
        // 获取二维码 ticket
        String url = QR_CODE.replace(ACCESS_TOKEN, accessToken);
        WechatConstant.QrCodeType qrCodeType;
        if (expireSeconds == null || expireSeconds <= 0) {
            qrCodeType = QrCodeType.QR_LIMIT_STR_SCENE;
        } else {
            qrCodeType = QrCodeType.QR_STR_SCENE;
            sceneId = RandomStringUtils.randomNumeric(32);
        }
        WechatQrCodeRequest request = new WechatQrCodeRequest(sceneId, content, qrCodeType.name());
        request.setExpire_seconds(expireSeconds);
        String requestBody = JSONUtil.toJsonStr(request);
        String qrResp = HttpUtil.post(url, requestBody);
        log.info("WechatOfficialAccountApi.createQrCode: {} ， resp = {}", requestBody, qrResp);
        WechatQrCodeTicket qrCodeTicket = JSONUtil.toBean(qrResp, WechatQrCodeTicket.class);
        if (!Objects.equals(qrCodeTicket.getErrcode(), WECHAT_RESPONSE_SUCCESS)) {
            log.info("WechatApi.createQrCode 创建微信二维码失败, errcode={}, errmsg={}", qrCodeTicket.getErrcode(), qrCodeTicket.getErrmsg());
//            throw new JeecgBootException("创建微信二维码失败");
            return null;
        }
        log.info("WechatOfficialAccountApi.createQrCode.[accessToken, sceneId, content, expireSeconds] qrCodeTicket=" + qrCodeTicket);
        // 返回查看二维码地址
        qrCodeTicket.setQrCodeUrl(GET_QR_CODE.replace(TICKET, URLUtil.encode(qrCodeTicket.getTicket())));
        return qrCodeTicket;
    }

    @Override
    public String getRidInfo(AccessToken accessToken, WechatRidRequest request) {
        return doPost(QUERY_RID, accessToken.getAccessToken(), request);
    }

}
